/*
 * Copyright (c) 2014-2015 Luca Agostini, Alessandro Cantini, Alessandro Dignani
 *
 * This file is part of MeteoCal.
 *
 * MeteoCal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.polimi.se2.agostini_cantini_dignani.meteocal.business;

import it.polimi.se2.agostini_cantini_dignani.meteocal.entity.Event;
import it.polimi.se2.agostini_cantini_dignani.meteocal.entity.WeatherForecast;
import it.polimi.se2.agostini_cantini_dignani.meteocal.model.TemperatureUnit;
import it.polimi.se2.agostini_cantini_dignani.meteocal.util.DateTimeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import java.util.*;
import java.util.logging.Logger;

/**
 * This class manages the business logic related to weather forecasts.
 *
 * @author Luca Agostini <luca1.agostini@mail.polimi.it>
 */
@Stateless
public class WeatherForecastManager {

	public static final List<String> BAD_WEATHER_STRINGS = Arrays.asList(
			"rain", "snow", "thunderstorm", "sleet", "drizzle"
	);
	public static final int DAYS_TO_FORECAST = 7;
	private static final Logger LOGGER = Logger.getLogger(WeatherForecastManager.class.getName());

	@EJB
	private WeatherForecastCache cache;
	@EJB
	private WeatherForecastRetrieval weatherForecastRetrieval;

	/**
	 * Returns the weather forecast related to event location and event date.
	 *
	 * @param place       the event location
	 * @param date        the event date
	 * @param mustBeQuick if the forecast must be fast, namely without interacting with server
	 * @return the forecast
	 */
	public WeatherForecast getForecast(String place, Date date, boolean mustBeQuick) {
		if (!canBeForecast(place, date)) {
			return null;
		}

		WeatherForecast forecast;

		forecast = cache.getForecast(place, date);
		if (forecast != null) {
			LOGGER.fine("Weather forecast retrieved from the cache");
			return forecast;
		}
		if (mustBeQuick) {
			return null;
		}

		forecast = weatherForecastRetrieval.retrieveWeatherForecast(place, date);

		if (forecast == null) {
			return null;
		}

		cache.addForecast(place, date, forecast);
		LOGGER.fine("Weather forecast retrieved from the server");
		return forecast;
	}

	/**
	 * Returns true if weather is bad.
	 *
	 * @param wf the forecast to be analyzed
	 * @return true if weather is bad
	 */
	public boolean isBad(String wf) {

		for (String s : BAD_WEATHER_STRINGS) {
			if (StringUtils.containsIgnoreCase(wf, s)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns true if weather is bad.
	 *
	 * @param wf the forecast to be analyzed
	 * @return true if weather is bad
	 */
	public boolean isBad(WeatherForecast wf) {
		return isBad(wf.getWeather());
	}

	/**
	 * Returns an alternative date for a given event.
	 *
	 * @param event the event to move
	 * @return the alternative date; if there is not an alternative date it returns null
	 */
	public Date findAlternativeDate(Event event) {
		if (event.getWeatherForecast() == null || !isBad(event.getWeatherForecast()) || !event.isOutdoor()) {
			return null;
		}

		List<WeatherForecast> sevenDaysForecasts = weatherForecastRetrieval.retrieveWeeklyForecasts(event.getPlace());
		Date alternativeDate = null;
		for (int i = sevenDaysForecasts.size() - 1; i > 0; i--) {
			if (!isBad(sevenDaysForecasts.get(i))) {
				Long currentMillis = System.currentTimeMillis();
				Date tempDate = new Date(currentMillis + i * DateUtils.MILLIS_PER_DAY);
				if (alternativeDate == null || DateTimeUtils.distanceInDays(event.getStartTime(), alternativeDate) > DateTimeUtils.distanceInDays(event.getStartTime(), tempDate)) {
					alternativeDate = tempDate;
				}
			}
		}
		if (alternativeDate == null) {
			return null;
		}

		GregorianCalendar alternativeCal = new GregorianCalendar();
		alternativeCal.setTime(alternativeDate);
		GregorianCalendar eventCal = new GregorianCalendar();
		eventCal.setTime(event.getStartTime());

		alternativeCal.set(Calendar.HOUR_OF_DAY, eventCal.get(Calendar.HOUR_OF_DAY));
		alternativeCal.set(Calendar.MINUTE, eventCal.get(Calendar.MINUTE));
		alternativeCal.set(Calendar.SECOND, eventCal.get(Calendar.SECOND));

		return alternativeCal.getTime();
	}

	public String getTemperatureString(WeatherForecast weatherForecast, TemperatureUnit unit) {
		Float temp = TemperatureUnit.convert(weatherForecast.getTemperature(), WeatherForecast.ENTITY_TEMPERATURE_UNIT, unit);

		return String.format("%.1f", temp) + unit.getSymbol();
	}

	protected boolean canBeForecast(String place, Date date) {
		if (place == null || date == null) {
			return false;
		}
		final Date now = new Date();

		return date.after(now) &&
				DateTimeUtils.distanceInDays(now, date) < DAYS_TO_FORECAST;
	}
}
